using System;
using System.Diagnostics;
using CriticalSystems.Instrumentation.Configuration;
using CriticalSystems.Instrumentation.Internal;

namespace CriticalSystems.Instrumentation.LogWriters
{
    internal class EventLogWriter : ILogWriter
    {
        private readonly int eventLogCategory;
        private readonly IInstrumentationConfiguration configuration;
        private readonly EventLog eventLog = new EventLog();

        public EventLogWriter(string log, int eventLogCategory)
        {
            this.eventLogCategory = eventLogCategory;
            this.configuration = InstrumentationConfiguration.Instance;
            InitializeEventSource(log);
        }

        internal EventLogWriter(string log, int eventLogCategory, IInstrumentationConfiguration configuration)
        {
            this.eventLogCategory = eventLogCategory;
            this.configuration = configuration;
            InitializeEventSource(log);
        }

        private void InitializeEventSource(string log)
        {
            this.eventLog.Log = log;
            this.eventLog.Source = this.configuration.System;
        }

        public void LogIn(BoundaryType boundaryType, LogSource source)
        {
            LogBoundaryAction(LoggingAction.Entry, boundaryType, source);
        }

        public void LogOut(BoundaryType boundaryType, LogSource source)
        {
            LogBoundaryAction(LoggingAction.NormalExit, boundaryType, source);
        }

        public void LogOut(BoundaryType boundaryType, LogSource source, ExceptionInfo exceptionInfo)
        {
            EventInstance instance = new EventInstance(GetEventId(boundaryType, LoggingAction.AbnormalExit), this.eventLogCategory);
            instance.EntryType = EventLogEntryType.Error;
            WriteSourceException(instance, source, exceptionInfo);
        }

        public void LogInfo(LogSource source, string message)
        {
            if ((this.configuration.Logging & LoggingFlags.LogInformation) != 0)
            {
                EventInstance instance = new EventInstance(GetEventId(BoundaryType.None, LoggingAction.Information), this.eventLogCategory);
                instance.EntryType = EventLogEntryType.Information;
                WriteSourceMessage(instance, source, message);
            }
        }

        public void LogWarning(LogSource source, string message)
        {
            if ((this.configuration.Logging & LoggingFlags.LogWarning) != 0)
            {
                EventInstance instance = new EventInstance(GetEventId(BoundaryType.None, LoggingAction.Warning), this.eventLogCategory);
                instance.EntryType = EventLogEntryType.Warning;
                WriteSourceMessage(instance, source, message);
            }
        }

        public void LogError(LogSource source, string message)
        {
            if ((this.configuration.Logging & LoggingFlags.LogError) != 0)
            {
                EventInstance instance = new EventInstance(GetEventId(BoundaryType.None, LoggingAction.Exception), this.eventLogCategory);
                instance.EntryType = EventLogEntryType.Error;
                WriteSourceMessage(instance, source, message);
            }
        }

        public void LogError(LogSource source, ExceptionInfo exceptionInfo)
        {
            EventInstance instance = new EventInstance(GetEventId(BoundaryType.None, LoggingAction.Error), this.eventLogCategory);
            instance.EntryType = EventLogEntryType.Error;
            WriteSourceException(instance, source, exceptionInfo);
        }

        private void LogBoundaryAction(LoggingAction action, BoundaryType boundaryType, LogSource source)
        {
            EventInstance instance = new EventInstance(GetEventId(boundaryType, action), this.eventLogCategory);
            instance.EntryType = EventLogEntryType.Information;
            WriteSource(instance, source);
        }

        private void WriteSource(EventInstance instance, LogSource source)
        {
            WriteEvent(instance, source.Subsystem, source.Instance, source.Module, source.Component, source.Function,
                       source.Process, source.Thread, source.Identity);
        }

        private void WriteSourceException(EventInstance instance, LogSource source, ExceptionInfo exceptionInfo)
        {
            WriteEvent(instance, source.Subsystem, source.Instance, source.Module, source.Component, source.Function,
                       source.Process, source.Thread, source.Identity, exceptionInfo.Id,
                       exceptionInfo.Exception.GetType().FullName, exceptionInfo.Exception.Message,
                       exceptionInfo.Properties, exceptionInfo.Tracing, exceptionInfo.Exception.StackTrace);
        }

        private void WriteSourceMessage(EventInstance instance, LogSource source, string message)
        {
            WriteEvent(instance, source.Subsystem, source.Instance, source.Module, source.Component, source.Function,
                       source.Process, source.Thread, source.Identity, message);
        }

        private void WriteEvent(EventInstance instance, params object[] parameters)
        {
            try
            {
                this.eventLog.WriteEvent(instance, parameters);
            }
            catch (Exception ex)
            {
                Log.Trace.Error("WriteEvent", ex);
            }
        }

        internal static long GetEventId(BoundaryType boundaryType, LoggingAction action)
        {
            return (long)boundaryType + (long)action;
        }

        internal enum LoggingAction
        {
            Entry = 0,
            NormalExit = 1,
            AbnormalExit = 2,
            Information = 0,
            Warning = 1,
            Error = 2,
            Exception = 3,
        }
    }
}